package libnetdb.dm.assoc.base;

import java.util.*;

/**
 * <code>RuleSet</code> is where rules are generated. One can get
 * rules from rule set by the <code>RuleCollector</code>. In this
 * implementations one can obtain all the rules satisfied the
 * requirement of support and confidence, rules with specific
 * target itemset, or the rules consists of the specified element.
 *
 * @author Cheng-Ru Lin
 * @version 1.0
 */
public class RuleSet {
	private Map map;
	private int tranx_numb;
	private double intrinsic;
	private LitemsetNode root;
    private NameSpace nameSpace = new DefaultNameSpace();

	RuleSet(int tranx_numb, Map map, double support) {
		this.tranx_numb = tranx_numb;
		this.map = map;
		this.intrinsic = support;
		this.root = getLatticeNode(new Itemset(new int[0]));
	}

	Rule createRule(LitemsetNode all, LitemsetNode node) {
		return new Rule(this, all, node);
	}

	LitemsetNode getLatticeNode(Itemset itemset) {
		return (LitemsetNode) map.get(itemset);
	}
    
    public String toString(Itemset itemset){
        StringBuffer strbuf = new StringBuffer("{");
        if(itemset.ids.length > 0)
            strbuf.append(nameSpace.getName(itemset.ids[0]));
        for(int i=1;i<itemset.ids.length;++i)
            strbuf.append(", ").
                    append(nameSpace.getName(itemset.ids[i]));
        strbuf.append("}");
        return strbuf.toString();
    }
    
    public void setNameSpace(NameSpace nameSpace){
        this.nameSpace = nameSpace;
    }
    
    public NameSpace getNameSpace(){
        return this.nameSpace;
    }

	/**
	 * Creates a rule with the specified cuase set and consequence
	 * set.
	 *
	 * @param cause the cause set of the constructed rule
	 * @param consequence the consequence set of the constructed rule
	 * @return the new created rule
	 */
	public Rule createRule(Itemset cause, Itemset consequence) {
		LitemsetNode all_node =
			getLatticeNode(Itemset.merge(cause, consequence)),
			cause_node = getLatticeNode(cause);
		return all_node == null ? null : createRule(all_node, cause_node);
	}

	/**
	 * Gets the transaction number of the rule set.
	 *
	 * @return the transction number of this rule set.
	 */
	public int getTransactionCount() {
		return tranx_numb;
	}

	/**
	 * Gets the count of the large itemset with the specified
	 * itemset. This function will find the large itemset in this
	 * rule set. If the specified large itemset cannot be found,
	 * this function will return a nagtive value. Otherwise, the
	 * count of that large itemset will be returned.
	 *
	 * @param itemset the itemset of the quaried large itemset
	 * @return the count of the specified large itemset; -1 if such
	 *    large itemset cannot be found
	 */
	public int getCount(Itemset itemset) {
		LitemsetNode node = getLatticeNode(itemset);
		return node == null ? -1 : node.getLitemset().count;
	}

	/**
	 * Gets the intrinsic support of this rule set. The intrinsic
	 * support is the value of support used in the mining process.
	 *
	 * @return the intrinsic support of this rule set
	 */
	public double getIntrinsicSupport() {
		return intrinsic;
	}
    
    /**
     * Gets the rules satisfied the specified support and confidence
     * threshold.
     * 
     * @param support the support threshold
     * @param confidence the confidence threshold
     * @return the rules satisfied the given support and confidence 
     *   threshold
     */
	public Iterator getRules(double support, double confidence) {
        checkSupport(support);
		return new RuleIterator(support, confidence);
	}
    
    /**
     * Gets the litemset view of each items in this rule set.
     * Each returned object is an instance of Litemset, which 
     * means you can obtain the id of the item by the member
     * <code>ids[0]</ocde> and get the count of this item by
     * the other menber <code>count</code>.
     *  
     * @return an iterator of each item in class of itemset
     */
    public Iterator getItems(){
        return new ItemIterator(root);
    }

    /**
     * Gets the litemset view of the ancensters of the specified
     * itemset.
     * 
     * @param itemset the specified itemset
     * @return a LatticeIterator of the ancesters
     */
    public LatticeIterator ancensterIterator(Itemset itemset){
        return new AncensterIterator(getLatticeNode(itemset));
    }
    
    /**
     * Gets the litemset view of the decensters of the specified
     * itemset.
     * 
     * @param itemset the specified itemset
     * @return a LatticeIterator of the decesters
     */
    public Iterator decensterIterator(Itemset itemset){
        return new DecensterIterator(getLatticeNode(itemset));
    }

    /**
     * Gets the recommended items base on the specified itemset.
     * This recommendation is made base on all the rules in this
     * rule set. Note that each returned recommendation consists of
     * two parts, i.e., item and confidence, which mean the
     * recommendatted item and the highest confidence of all
     * supported rules respectively. We say a rule support an item,
     * if the cause set of the rule is a subset of the given itemset
     * and the item is an element of the consequence set.
     *
     * @param ids the itemset which the commendations are
     *   made base on
     * @return an array of the recommended items base on the given
     *   itemset
     */
    public Iterator getRecommendRules(
            int ids[], double support, double confidence){
        checkSupport(support);
        return new RecommendIterator(ids, support, confidence);
    }

    private void checkSupport(double support){
        if(support < intrinsic)
            throw new IllegalArgumentException("support too small");
    }
    
    private class RecommendIterator implements Iterator {

        private Iterator iter, viter = null;
        private LitemsetNode localRoot;
        private Itemset itemset;
        private HashSet toVisitSet = new HashSet();
        private Hashtable itemMap = new Hashtable();
        private double support, confidence;
        private int conf_lmt;
        private Object nextObject = null;

        public RecommendIterator(
                int ids[], double support, double confidence) {
            this.itemset = new Itemset(ids);
            this.support = support;
            this.confidence = confidence;
            this.iter = root.getParents().iterator();
            this.localRoot = root;
            this.conf_lmt = (int) Math.ceil(
                    root.getLitemset().count * confidence);
            this.nextObject = findNext();
        }

        protected Object findNext() {
            while(true){
                while(iter.hasNext()){
                    LitemsetNode node = (LitemsetNode) iter.next();
                    Integer id = new Integer((node.getLitemset().
                            subtract(localRoot.getLitemset())).ids[0]);
                    if(!itemset.contains(id.intValue())){
                        if(node.getLitemset().count >= conf_lmt)
                            return createRule(node, localRoot);
                    }else toVisitSet.add(node);
                }
                if(viter != null && viter.hasNext()){
                    localRoot = (LitemsetNode) viter.next();
                    iter = localRoot.getParents().iterator();
                    this.conf_lmt = (int) Math.ceil( localRoot.
                            getLitemset().count * confidence);
                }else if(!toVisitSet.isEmpty()){
                    viter = toVisitSet.iterator();
                    toVisitSet = new HashSet();                    
                }else return null;
            }
        }
        
        public Object next(){
            Object current = nextObject;
            this.nextObject = findNext();
            return current;
        }
        
        public boolean hasNext(){
            return nextObject != null;
        }
        
        public void remove(){
            throw new UnsupportedOperationException("remove");            
        }
    }
    
    private static class AncensterIterator extends LatticeIterator{        
        public AncensterIterator(LitemsetNode root){
            super(root);
        }
        
        protected Collection nextLevel(Object object) {
            return ((LitemsetNode)object).getParents();
        }

        public Object next() {
            LitemsetNode node = (LitemsetNode) super.next();
            return node.getLitemset();
        }
    }

    private static class DecensterIterator extends LatticeIterator{        
        public DecensterIterator(LitemsetNode root){
            super(root);
        }
        
        protected Collection nextLevel(Object object) {
            return ((LitemsetNode)object).getChildren();
        }

        public Object next() {
            LitemsetNode node = (LitemsetNode) super.next();
            return node.getLitemset();
        }
    }

	private class RuleIterator implements Iterator {

		private LatticeIterator iter;
		private BasicRuleIterator riter;
		private double confidence;
        private int support_lmt;

		public RuleIterator(double support,	double confidence) {
			
            this.support_lmt = (int)Math.ceil(tranx_numb * support);
			this.confidence = confidence;
			this.iter = RuleSet.this.root.ancestorIterator();
			this.riter = new BasicRuleIterator(
                	RuleSet.this, RuleSet.this.root, confidence);
		}

		public boolean hasNext() {
			while(!riter.hasNext()){
                if(iter.hasNext()){
                    LitemsetNode node = (LitemsetNode) iter.next();
                    if(node.getLitemset().count >= support_lmt)
                        riter = new BasicRuleIterator( 
                                RuleSet.this, node, confidence);
                    else iter.hardPrune();            
                }else return false; 
			}
            return true;
		}

		public Object next() {
            return riter.next();
		}

		public void remove() {
			throw new UnsupportedOperationException("remove");
		}
	}
    
    private static class DefaultNameSpace implements NameSpace{
        static final String PREFIX = "Item:";
        public String getName(int id){
            return PREFIX + String.valueOf(id);
        }
        
        public int getID(String name){
            if(name.startsWith(PREFIX)){
                return Integer.parseInt(
                        name.substring(PREFIX.length()));            
            }else return -1;
        }
        
        public int registerName(String name){
            throw new UnsupportedOperationException();
        }        
    }
    
    private static class ItemIterator implements Iterator{

        private Iterator iter;

        public ItemIterator(LitemsetNode root){
            iter = root.getParents().iterator();            
        }
        
        public boolean hasNext(){
            return iter.hasNext();
        }
        
        public Object next(){
            LitemsetNode node = (LitemsetNode) iter.next();
            return node==null ? null : node.getLitemset();        
        }
        
        public void remove(){
            throw new UnsupportedOperationException("remove");
        }         
    }

}